package ch.bfh.ti.barcode.multiple;

import java.util.Arrays;

/**
 * The data has to be splitted into small parts to be transfered with barcodes.
 * Those parts have to carry some additional data like the frame number to
 * identify every single frame. This class helps create and read those parts.
 * 
 * @author Dominik von Burg
 * 
 */
class Frame {
	protected static final int headerSize = 50;
	protected static final int filenameSize = 41;
	protected static final int frameNumberSize = 2;

	protected byte[] frame;

	/**
	 * Calculates the size of the data portion of the first frame. The size of
	 * the whole frame has to be provided. Basically the method just subtracts
	 * the size of the header of the provided frame size.
	 * 
	 * @param frameSize
	 *            The size of the whole frame.
	 * @return The user data portion size of the frame.
	 */
	public static int getFirstFrameDataSize(int frameSize) {
		return frameSize - headerSize;
	}

	/**
	 * Calculates the size of the data portion of the simple frame. The size of
	 * the whole frame has to be provided. Basically the method just subtracts
	 * the size of the header of the provided frame size.
	 * 
	 * @param frameSize
	 *            The size of the whole frame.
	 * @return The user data portion size of the frame.
	 */
	public static int getSimpleFrameDataSize(int frameSize) {
		return frameSize - frameNumberSize;
	}

	/**
	 * Reads all the data from the frame. The header is included. This method
	 * can be used to get the raw data of the frame to encode the frame.
	 * 
	 * @return The raw data of the frame with the header included.
	 */
	public byte[] getFrameData() {
		return frame;
	}

	/**
	 * Reads the user data from the frame. The header is being trimmed away from
	 * the data. This method can be used to read the encapsulated data from the
	 * frame.
	 * 
	 * @return The user data of the frame.
	 */
	public byte[] getData() {
		int from;
		if (this instanceof HeaderFrame)
			from = headerSize;
		else
			from = frameNumberSize;

		int to = frame.length;

		return Arrays.copyOfRange(frame, from, to);
	}

	/**
	 * Reads the frame number from the header of this frame. The frame number is
	 * saved in the first two bytes of the frame.
	 * 
	 * @return The frame number of this frame.
	 */
	public int getFrameNumber() {
		int number = frame[0] & 0xff;
		number += (frame[1] & 0xff) << 8;
		return number;
	}

	/**
	 * Modifies the frame number in the header of this frame. The frame number
	 * is saved in the first two bytes of the frame.
	 * 
	 * @param frameNumber
	 *            The new frame number that will be allocated to this frame.
	 */
	public void setFrameNumber(int frameNumber) {
		frame[0] = (byte) ((frameNumber << 24) >>> 24);
		frame[1] = (byte) ((frameNumber << 16) >>> 24);
	}

	/**
	 * Creates a new instance of Frame. The method analyzes the header of the
	 * frame and decides which subtype of Frame it has to instantiate.
	 * 
	 * @param data
	 *            The raw byte-data of the frame.
	 * @return An instance of the type Frame white the provided data.
	 */
	public static Frame createFrameFromData(byte[] data) {
		int fn = data[0] & 0xff;
		fn += (data[1] & 0xff) << 8;

		if (fn == 0)
			return new HeaderFrame(data);
		else
			return new SimpleFrame(data);
	}

	/**
	 * Restores a missing frame by XORing the provided frames. Creates a new
	 * SimpleFrame with the restored data. The method can restore only one
	 * frame. If there are two frames of an error correction block missing,
	 * there is no way to restore the missing frames.
	 * 
	 * @param frames
	 *            All the received frames of an error correction block.
	 * @return The restored frame.
	 */
	public static Frame restore(Frame[] frames) {
		byte[] restored = new byte[frames[0].frame.length];
		for (Frame c : frames)
			for (int i = 0; i < c.frame.length; i++)
				restored[i] ^= c.frame[i];

		return new SimpleFrame(restored);
	}

	/**
	 * Creates the error correction frame of an error correction block. All the
	 * frames of a block have to be provided. The correction frame is being
	 * calculated by XORing all the frames.
	 * 
	 * @param frames
	 *            The frames of a whole error correction block.
	 * @return All the provided frames plus the error correction frame.
	 */
	public static Frame[] createCorrection(Frame[] frames) {
		byte[] correctionFrame = new byte[frames[0].getData().length];

		for (Frame c : frames) {
			c.setFrameNumber(c.getFrameNumber() + 1);
			byte[] data = c.getData();
			for (int i = 0; i < data.length; i++)
				correctionFrame[i] ^= data[i];
		}

		Frame[] correctedFrames = new Frame[frames.length + 1];
		correctedFrames[0] = new SimpleFrame(correctionFrame,
				frames[0].getFrameNumber() - 1);
		for (int i = 0; i < frames.length; i++)
			correctedFrames[i + 1] = frames[i];

		return correctedFrames;
	}
}
